/*
 * ============================================================================
 *
 *       Filename:  comment.em
 *
 *    Description:
 *
 *        Version:  1.0
 *        Created:  2019/5/4 1:44:53
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  bluce.lee (), blucerlee@gmail.com
 *   Organization:  CN
 *
 * ============================================================================
 */

//
// Comment the selected block of text using single line comments and indent it
//
/*
lnFirst
	the first line number
ichFirst
	the index of the first character on the line lnFirst
lnLast
	the last line number
ichLim
	the limit index (one past the last) of the last character on
	the line given in lnLast
fExtended
	TRUE if the selection is extended to include more than one character.
	FALSE if the selection is a simple insertion point.
	this is the same as the following expression:
	(sel.fRect || sel.lnFirst != sel.lnLast || sel.ichFirst !=
	sel.ichLim)
fRect
	TRUE if selection is rectangular (block style).
	FALSE if the selection is a linear range of characters.
The following fields only apply if fRect is TRUE:
xLeft
	the left pixel position of the rectangle in window coordinates.
xRight
	the pixel position of the right edge of the rectangle in window coordinates.

*/

macro strchr(str, chr)
{
	len = strlen(str);
	i = 0;

	if(len == 0) {
		return nil;
	}

	while(str[i] != chr) {
		i++;
		if(i >= len) {
			return nil;
		}
	}

	return strmid(str, i, len);
}

macro strncmp(s1, s2, cnt)
{
	len1 = strlen(s1);
	len2 = strlen(s2);

	if(len1 < cnt || len2 < cnt || cnt == 0) {
		msg("i error cnt")
		return FALSE;
	}

	i = 0;
	while(s1[i] == s2[i]) {
		i++;
		if(i > cnt) {
			return TRUE;
		}
	}

	if(i == cnt) {
		return TRUE
	} else {
		//msg("i != cnt")
		return FALSE
	}
}

macro strstr(s1, s2)
{
	p = s1;
	len1 = strlen(s1);
	len2 = strlen(s2);

	if (len1 == 0 || len2 == 0) {
		return nil;
	}

	i = 0;
	//msg("strstr() s1:@s1@ s2:@s2@")
	p = strchr(s1, s2[i]);
	while(p != nil) {
		//msg("strstr() p:@p@ s2:@s2@")

		if(strncmp(p, s2, len2) == TRUE) {
			return strmid(s1, i, len1);
		}
		i++;
		p = strchr(s1, s2[i]);
	}

	return nil;
}

macro is_start_with_str(s1, s2)
{
	len1 = strlen(s1)
	len2 = strlen(s2)

	if (len1 < len2) {
		return FALSE
	}

	ret = strncmp(s1, s2, len2)
	//msg("is_start_with_str() s1:@s1@ s2:@s2@ ret:@ret@")
	return ret
}

macro is_end_with_str(s1, s2)
{
	len1 = strlen(s1)
	len2 = strlen(s2)

	if (len1 < len2) {
		return FALSE
	}

	str = strmid(s1, len1-len2, len1)
	ret = strncmp(str, s2, len2)
	//msg("is_end_with_str() s1:@s1@ s2:@s2@ str:@str@ ret:@ret@")
	return ret
}

macro trim_str(szLine)
{
	szLine = trim_left(szLine);
	szLIne = trim_right(szLine);
	return szLine;
}

macro trim_left(szLine)
{
	len = strlen(szLine)
	if(len == 0) {
		return nil
	}

	i = 0
	while(szLine[i] == " " || szLine[i] == "\t") {
		i++;
		if(i >= len) {
			return nil
		}
	}

	return strmid(szLine, i, len)
}

macro trim_right(szLine)
{
	len = strlen(szLine)
	if(len == 0) {
		return szLine
	}

	i = len
	while(i > 0)
	{
		i = i - 1
		if((szLine[i] != " ") && (szLine[i] != "\t") &&
		   (szLine[i] != "\r") && (szLine[i] != "\n"))
		{
			break
		}
	}

	return strmid(szLine, 0, i+1)
}

macro get_indent_str(szLine, szLimit)
{
	cnt = 0
	//msg("good @cnt@ @szLimit@ @szLine@")
	while(szLine[cnt] == " " || szLine[cnt] == "\t"){
		cnt++;
		if(cnt >= szLimit) {
			msg("error cnt:@cnt@")
			return nil
		}
	}

	str = strmid(szline, 0, cnt)
	//msg("good @cnt@ @str@")
	return str
}

macro get_real_str(szLine, szLimit)
{
	cnt_start = 0

	//msg("szLimit:@szLimit@  szLine:@szLine@")
	while(szLine[cnt_start] == " " || szLine[cnt_start] == "\t"){
		cnt_start++;
		if(cnt_start >= szLimit) {
			msg("error cnt_start:@cnt_start@")
			return nil
		}
	}

	cnt_end = szLimit
	while(szLine[cnt_end] == " " || szLine[cnt_end] == "\t"){
		cnt_end--;
		if(cnt_end == 0) {
			msg("error cnt_end:@cnt_end@")
			return nil
		}
	}

	str = strmid(szline, cnt_start, cnt_end)
	//msg("good str:@str@")
	return str
}

macro __comment_block(hbuf, iStart, iEnd)
{
	cmt_start = "/*";
	cmt_end   = "*/";

	if(iStart >= iEnd) {
		return 0;
	}

	stLine = GetBufLine(hbuf, iStart);
	stLine = trim_str(stLine);
	edLine = GetBufLine(hbuf, iEnd);
	edLine = trim_str(edLine);

	//msg("__comment_block() stLine:@stLine@  edLine:@edLine@")
	if(is_start_with_str(stLine, cmt_start) == TRUE &&
	   is_end_with_str(edLine, cmt_end) == TRUE) {
		stLine = strmid(stLine, strlen(cmt_start), strlen(stLine));
		mdlen = 0
		if(stLine != nil) {
			PutBufLine(hbuf, iStart, stLine);
		} else {
			DelBufLine(hbuf, iStart)
			// if line iStart is deleted, so the count of lines should substract one
			mdlen = 1
		}

		elen = strlen(edLine);
		edLine = strmid(edLine, 0, elen - strlen(cmt_end))
		if(stLine != nil) {
			PutBufLine(hbuf, iEnd, edLine);
		} else {
			DelBufLine(hbuf, iEnd-mdlen)
			mdlen = mdlen + 1
		}
		//msg("__comment_block() ret stLine:@stLine@  edLine:@edLine@")
		return -mdlen;
	}

	insbufline(hbuf, iStart, cmt_start);
	insbufline(hbuf, iEnd + 2, cmt_end);
	return 2;
}

macro __comment_line(hbuf, iLine)
{
	cmt_start = "/*";
	cmt_end = "*/";

	szLine = GetBufLine(hbuf, iLine);
	szLimit = GetBufLineLength(hbuf, iLine);
	lineSkip = get_indent_str(szLine, szLimit);
	szLine = trim_str(szLine);

	if(is_start_with_str(szLine, cmt_start) == TRUE) {
		str = strmid(szLine, strlen(cmt_start), strlen(szLine) - strlen(cmt_end));
		str = trim_str(str)
		snLine = cat(lineSkip, str);
		PutBufLine(hbuf, iLine, snLine);
		ret = strlen(snLine) - szLimit;
		//msg("__comment_line() szLimit:@szLimit@ ret:@ret@")
		return ret
	}

	snLine = cat(lineSkip, cmt_start);
	snLine = cat(snLine, " ");
	snLine = cat(snLine, szLine);
	snLine = cat(snLine, " ");
	snLine = cat(snLine, cmt_end);
	PutBufLine(hbuf, iLine, snLine);
	return strlen(snLine) - szLimit;
}

macro __comment_select(hbuf)
{
	cmt_start = "/*";
	cmt_end   = "*/";

	istr = GetBufSelText(hbuf);
	len = strlen(istr);
	tStr = trim_str(istr)
	//msg("__comment_select() istr:@istr@ trim_str:@str@");

	if(is_start_with_str(tStr, cmt_start) == TRUE &&
	   is_end_with_str(tStr, cmt_end) == TRUE) {
		snLine = strmid(tStr, strlen(cmt_start), strlen(tStr));
		snLine = strmid(snLine, 0, strlen(snLine)-strlen(cmt_end))
		snLine = trim_str(snLine)
		SetBufSelText(hbuf, snLine);
		ret = strlen(snLine) - len;
		//msg("__comment_select() unm istr:@istr@ snLine:@snLine@ ret:@ret@");
		return ret
	}

	snLine = cat(cmt_start, " ")
	snLine = cat(snLine, tStr);
	snLine = cat(snLine, " ")
	snLine = cat(snLine, cmt_end);
	//msg("__comment_select: istr@istr@ str:@str@");
	SetBufSelText(hbuf, snLine);
	ret = strlen(snLine) - len;
	//msg("__comment_select() cmt istr:@istr@ snLine:@snLine@ ret:@ret@");
	return ret
}

macro CommentBlock()
{
	hbuf = GetCurrentBuf();
	hwnd = GetCurrentWnd();
	sel = GetWndSel(hwnd);

	// insert a new line before hbuf for set /*
	if(sel.lnFirst == sel.lnLast) {
		len = GetBufLineLength(hbuf, sel.lnFirst);
		llen = sel.ichLim - sel.ichFirst;
		if(sel.ichFirst != sel.ichLim && llen != len) {
			//msg("__comment_select llen:@llen@")
			sel.ichLim = sel.ichLim + __comment_select(hbuf);
		} else {
			ret = __comment_line(hbuf, sel.lnFirst);
			if(sel.ichFirst != sel.ichLim) {
				sel.ichLim = sel.ichLim + ret;
			}
		}

	} else {
		// line count return
		// it must be -2 , 0, 2. 0-noChange, -2-unComment, 2-comment
		sel.lnLast = sel.lnLast + __comment_block(hbuf, sel.lnFirst, sel.lnLast);
		sel.ichFirst = 0
		sel.ichLim = GetBufLineLength(hbuf, sel.lnLast)
	}

	SetWndSel(hwnd, sel);
}

macro CommentPreDef()
{
	hwnd = GetCurrentWnd()
	hbuf = GetWndBuf (hwnd)
	sel = GetWndSel (hwnd)

	tabsz = _tsGetTabSize()
	msg("tabsz:@tabsz@")

	InsBufLine(hbuf,sel.lnFirst,"#if 0")
	InsBufLine(hbuf,sel.lnLast + 1,"#else")
	InsBufLine(hbuf,sel.lnLast + 2,"\t")
	InsBufLine(hbuf,sel.lnLast + 3,"#endif")
	SetBufIns (hbuf, sel.lnLast +2, 1)
}

//
// Undo the CommentBlock for the selected text.
//
macro UnCommentBlock()
{
	hbuf = GetCurrentBuf();
	hwnd = GetCurrentWnd();

	sel = GetWndSel(hwnd);

	iLine = sel.lnFirst;


	tabSize = 0;
	while (iLine <= sel.lnLast)
	{
		szLine = GetBufLine(hbuf, iLine);
		len = strlen(szLine);
		szNewLine = "";
		if (len > 1)
		{
			if (szLine[0] == "/" && szLine[1] == "/")
			{
				if (len > 2)
				{
					if (AsciiFromChar(szLine[2]) == 9)
					{
						tabSize = _tsGetTabSize() - 1;
						szNewLine = strmid(szLine, 3, strlen(szLine));
					}
				}

				if (szNewLine == "")
				{
					szNewLine = strmid(szLine, 2, strlen(szLine));
					tabSize = 2;
				}

				PutBufLine(hbuf, iLine, szNewLine);
			}
		}
		iLine = iLine + 1;
	}

	if (sel.lnFirst == sel.lnLast)
	{
		sel.ichFirst = sel.ichFirst - tabSize;
		sel.ichLim = sel.ichLim - tabSize;
	}

	SetWndSel(hwnd, sel);
}

macro _tsGetTabSize()
{
	szTabSize = GetReg("TabSize");

	if (szTabSize != "")
	{
		tabSize = AsciiFromChar(szTabSize[0]) - AsciiFromChar("0");
	}
	else
	{
		tabSize = 4;
	}

	return tabSize;
}

//
// Reformat a selected comment block to wrap text at 80 columns.
// The start of the selection (upper left most character of the selection) is
// handled specially, in that it specifies the left most column at which all
// lines will begin.  For example, if the following block was selected starting
// at the @ symbol, through the last line of the block...
//------------------------------------------------------------------------------
// preamble:	@ This is a line that will be wrapped keeping the "at" symbol in its current column.
// All lines following it that are selected will use that as their starting column.  See below to see how the wrapping
// works for this block of text.
//------------------------------------------------------------------------------
// preamble:	@ This is a line that will be wrapped keeping the "at" symbol in
//				its current column. All lines following it that are selected
//				will use that as their starting column.  See below to see how
//				the wrapping works for this block of text.
//
macro tsReformatCommentBlock()
{
	hbuf = GetCurrentBuf();
	hwnd = GetCurrentWnd();

	sel = GetWndSel(hwnd);

	tabSize = _tsGetTabSize();
	leftTextCol = 0 - 1;
	colWrap = 80;

	// Find the starting column, and create a Margin string
	ichFirst = sel.ichFirst;

	// Single line comment reformat?
	if (sel.ichFirst == sel.ichLim && sel.lnFirst == sel.lnLast)
	{
		ichFirst = 0;
	}

	rec = _tsGetStartColumn(hbuf, ichFirst, sel.lnFirst);

	if (rec == "")
		stop;

	colLeftMargin = rec.colMargin;
	szMargin = "";

	colComment = 0;
	if (rec.colComment >= 0)
	{
		colComment = rec.colComment + 2
		szMargin = _tsAddWhiteToColumn(szMargin, 0, rec.colComment, tabSize);
		szMargin = cat(szMargin, "//");
	}

	szMargin = _tsAddWhiteToColumn(szMargin, colComment, rec.colMargin, tabSize);

	rec = "";

	szCurMargin = "";
	if (ichFirst != 0)
	{
		szLine = GetBufLine(hbuf, sel.lnFirst);
		szCurMargin = strmid(szLine, 0, ichFirst);
	}
	else
	{
		szCurMargin = szMargin;
		szMargin = "";
	}

	insertLine = sel.lnFirst;
	iLine = sel.lnFirst;
	szRemainder = "";
	while (1)
	{
//		msg("$0-" # iLine # ":" # szRemainder);
		rec = _tsGetNextCommentString(hbuf, ichFirst, szRemainder, iLine, sel.lnLast, colWrap);
		ichFirst = 0;

		if (rec == "")
			break;

//		msg("$1-" # rec.ln # ":" # rec.szComment);
		szLine = rec.szComment;

		ich = 0;
		col = colLeftMargin;
		len = strlen(szLine);

		ichPrevCharToWhite = 0-1;
		ichPrevWhiteToChar = 0-1;
//		msg("Leftovers @szRemainder@");

		while (ich < len)
		{
			if (AsciiFromChar(szLine[ich]) == 9)
			{
				col = (((col + tabSize) / tabSize) * tabSize);
			}
			else
			{
				col = col + 1;
			}

			if (col > colWrap)
				break;

			fIsWhitespace = _tsIsWhitespaceChar(szLine[ich]);
			fIsWhitespace1 = 1;

			if (ich + 1 < len)
			{
				fIsWhitespace1 = _tsIsWhitespaceChar(szLine[ich + 1]);
			}

			if (!fIsWhitespace && fIsWhitespace1)
				ichPrevCharToWhite = ich;

			ich = ich + 1;
		}

		if (ichPrevCharToWhite > 0)
		{
//			msg("$2:" # strmid(szLine, 0, ichPrevCharToWhite + 1));
			ich = ichPrevCharToWhite + 1;

			while (ich < len)
			{
				if (!_tsIsWhitespaceChar(szLine[ich]))
				{
					ichPrevWhiteToChar = ich - 1;
//					msg("$3:" # strmid(szLine, ichPrevWhiteToChar + 1, len));
					break;
				}
				ich = ich + 1;
			}
		}

		if (ichPrevCharToWhite > 0 && col > colWrap)
		{
			szNewLine = cat(szCurMargin, strmid(szLine, 0, ichPrevCharToWhite + 1));
			szRemainder = "";
			if (ichPrevWhiteToChar > 0)
				szRemainder = strmid(szLine, ichPrevWhiteToChar + 1, len);

			if (ichPrevCharToWhite > ichPrevWhiteToChar)
				msg("!!!Wrap, duplicating word " # ichPrevWhiteToChar # " " # ichPrevCharToWhite # " " # szNewLine # " >>> " # szRemainder);
//				msg(szLine);
//				msg(col # " " # ichPrevWhiteToChar # " " # ichPrevCharToWhite # " " # szNewLine # " >>> " # szRemainder);
		}
		else if (szLine != "")
		{
			szNewLine = cat(szCurMargin, szLine );
			szRemainder = "";
//			sel.lnLast = sel.lnLast + 1;
		}

		iLine = rec.ln;
		if (insertLine == iLine)
		{
			iLine = iLine + 1;
			sel.lnLast = sel.lnLast + 1;

//			msg("$5-" # insertLine # ":" # szNewLine);
			InsBufLine(hbuf, insertLine, szNewLine);
		}
		else
		{
			szLine = GetBufLine(hbuf, insertLine);
			if (szLine != szNewLine)
			{
//				msg("$6-" # insertLine # ":" # szNewLine);
				PutBufLine(hbuf, insertLine, szNewLine);
			}
		}
		insertLine = insertLine + 1;

		if (szMargin != "")
		{
			szCurMargin = szMargin;
			szMargin = "";
		}
	}

	while (insertLine <= sel.lnLast)
	{
		DelBufLine(hbuf, insertLine);
		sel.lnLast = sel.lnLast - 1;
	}

	len = GetBufLineLength(hbuf, insertLine-1);

	sel.ichFirst = len;
	sel.ichLim = len;
	sel.lnFirst = sel.lnLast;
	SetWndSel(hwnd, sel);
}


macro _tsAddWhiteToColumn(sz, col0, col, tabSize)
{
	szTabs = "																														";
	szSpaces = "                ";

	tabs0 = col0 / tabSize;
	tabs = (col / tabSize) - tabs0;

	if (tabs == 0)
		foo = col0;
	else
		foo = (tabs + tabs0) * tabSize;
	spaces = col - foo;
//	msg(col0 # " " # col # " " # tabs # " " # spaces # " " # tabs0);

	if (tabs)
		sz = cat(sz, strmid(szTabs, 0, tabs));

	if (spaces)
		sz = cat(sz, strmid(szSpaces, 0, spaces));

	return sz;
}

macro _tsGetStartColumn(hbuf, ichBegin, ln)
{
	szLine = GetBufLine(hbuf, ln);
	len = strlen(szLine);
	tabSize = _tsGetTabSize();
	ich = 0;

	colMargin = 0;
	colComment = 0-1;

	rec = "";
	rec.colMargin = colMargin;
	rec.colComment = colComment;

	while (ich < len)
	{
		if (AsciiFromChar(szLine[ich]) == 9)
		{
			colMargin = (((colMargin + tabSize) / tabSize) * tabSize);
		}
		else
		{
			colMargin = colMargin + 1;
		}

		if (colComment < 0)
		{
			if (ich + 1 < len)
			{
				if (szLine[ich] == "/" && szLine[ich+1] == "/")
				{
					colComment = colMargin - 1;
					ich = ich + 2;
					colMargin = colMargin + 1;
					continue;
				}
			}
		}

		if (ich >= ichBegin)
		{
			if (!_tsIsWhitespaceChar(szLine[ich]))
			{
				rec.colMargin = colMargin - 1;
				rec.colComment = colComment;
//				msg(szLine[ich]);
				return rec;
			}
		}

		ich = ich + 1;
	}

	return rec;
}

macro _tsGetNextCommentString(hbuf, ichSkip, szRemainder, ln, lnLast, colWrap)
{
	rec = "";

	// Go until we get a string that is at least long enough to fill a line
	// or, we run out of lines.
	if (szRemainder == "" && ln > lnLast)
		return "";

	ichFirst = ichSkip;
//	msg(ichSkip);
	while (1)
	{
		if (ln > lnLast)
		{
			rec.szComment = szRemainder;
			rec.ln = ln;
			return rec;
		}

		cchRemainder = strlen(szRemainder);

		if (cchRemainder > colWrap)
		{
			rec.szComment = szRemainder;
			rec.ln = ln;
			return rec;
		}

		szLine = GetBufLine(hbuf, ln);
		len = strlen(szLine);

		if (ichSkip == 0)
			ichFirst = _tsSkipPastCommentAndWhitespace(szLine, len);
		ichSkip = 0;

		ichLast = len - 1;

		// Now, strip out all whitespace at the end of the line
		while (ichLast >= ichFirst)
		{
			if (!_tsIsWhitespaceChar(szLine[ichLast]))
			{
				break;
			}
			ichLast = ichLast - 1;
		}

		// Entire line is whitespace?
		if (ichLast < ichFirst)
		{
			if (szRemainder == "")
				ln = ln + 1;
			rec.szComment = szRemainder;
			rec.ln = ln;
			return rec;

		}
		// length of the non whitespaced comment + 1 space + cchRemainder
		if ((ichLast + 1) - ichFirst + cchRemainder + 1 > 255)
		{
			// It may not format the current line quite right, but
			// but at least we won't throw away some of the comment.
			rec.szComment = szRemainder;
			rec.ln = ln;
			return rec;
		}

		if (szRemainder != "")
			szRemainder = cat(szRemainder, " ");
		szRemainder = cat(szRemainder, strmid(szLine, ichFirst, ichLast + 1));
		ln = ln + 1;
	}
}



macro _tsSkipPastCommentAndWhitespace(szLine, len)
{
	ichFirst = 0;
	// Skip past the comment initiator "//" if there is one.
	while (ichFirst < len)
	{
		if (ichFirst + 1 < len)
		{
			if (szLine[ichFirst] == "/" && szLine[ichFirst+1] == "/")
			{
				ichFirst = ichFirst + 2;
				break;
			}
		}
		ichFirst = ichFirst + 1;
	}

	// If no comment found in line, then start from the beginning
	if (ichFirst >= len)
		ichFirst = 0;

	ichFirst = ichFirst;
	// Now, strip out all whitespace after the comment start.
	while (ichFirst < len)
	{
		if (!_tsIsWhitespaceChar(szLine[ichFirst]))
		{
			break;
		}
		ichFirst = ichFirst + 1;
	}

	return ichFirst;
}
